package org.putrpctorest.parameter.impl;

import java.beans.PropertyDescriptor;
import java.util.ArrayList;

import org.putrpctorest.PutRpcToRestError;
import org.putrpctorest.parameter.impl.log.MatchLog;
import org.putrpctorest.resource.CollectionType;
import org.putrpctorest.resource.Parameter;

public class Definitions extends ArrayList<Definition> {

	private static final long serialVersionUID = 1L;

	private String description;

	public Definitions(String description) {
		this.description = description;
	}

	public void add(Parameter parameter) {
		add(new Definition(parameter));
	}

	public void add(PropertyDescriptor propertyDescriptor, Definition root) {
		add(new Definition(propertyDescriptor, root));
	}
	
	public void add(String name, Class<?> type, CollectionType collectionType,
			String expression, Definition root) {
		add(new Definition(name, type, collectionType, expression, root));
	}

	public String getDescription() {
		return description;
	}

	public Match associate(Definitions other) {
		MatchLog matchLog = new MatchLog();

		if (other == null) {
			return Match.NO_MATCH;
		}
		Matcher matcher = new Matcher(this, other, matchLog);
		Match match = matcher.tryMatch();
		while (match == Match.NO_MATCH) {
			if (sameSize(other)) {
				if (flatten(matchLog) == null) {
					throw new PutRpcToRestError(
							"could not associate!  the complete test log is\n"
									+ matchLog);
				} else {
					match = flatten(matchLog)
							.associate(other.flatten(matchLog));
				}
			} else {
				Definitions flattned = getBetterCandidateForFlatting(other)
						.flatten(matchLog);
				if (flattned == null) {
					throw new PutRpcToRestError(
							"could not associate!  the complete test log is\n"
									+ matchLog);
				}
				match = flattned.associate(getWorstCandidateForFlatting(other));
			}
		}
		return match;
	}

	public boolean sameSize(Definitions other) {
		if (size() == other.size()) {
			return true;
		} else {
			return false;
		}
	}

	public Definitions getBetterCandidateForFlatting(Definitions other) {
		if (size() == other.size()) {
			return this;
		} else {
			if (size() < other.size()) {
				return this;
			} else {
				return other;
			}
		}
	}

	public Definitions getWorstCandidateForFlatting(Definitions other) {
		if (getBetterCandidateForFlatting(other) == this) {
			return other;
		} else {
			return this;
		}
	}

	public Definitions flatten(MatchLog matchLog) {
		Flattner flattner = new Flattner(this, matchLog);
		return flattner.flatten();
	}

	public Definition[] getInterchangeableTypes() {
		for (Definition outParam : this) {
			for (Definition inParam : this) {
				if (outParam != inParam) {
					if (outParam.type().isAssignableFrom(inParam.type())) {
						return new Definition[] { inParam, outParam };
					}
				}
			}
		}
		return null;

	}

	public String toString() {
		StringBuffer tmp = new StringBuffer();
		tmp.append(description);
		tmp.append("[");
		for (Definition definition : this) {
			tmp.append("[");
			tmp.append(definition.getName());
			tmp.append(",");
			tmp.append(definition.type().getName());
			tmp.append("]");
		}
		tmp.append("]");
		return tmp.toString();
	}

}
